// ==========================================
// 1. 全グローバル変数の定義（未定義エラー防止）
// ==========================================

// 基準星のデータ (名前, 表面温度[K], 光度[L/L☉])
const referenceStars = [
    { name: "太陽", temp: 5778, lum: 1 },
    { name: "シリウスA", temp: 9940, lum: 25.4 },
    { name: "シリウスB", temp: 25200, lum: 0.026 },
    { name: "ベガ", temp: 9602, lum: 40.12 },
    { name: "スピカ", temp: 22400, lum: 12100 },
    { name: "プロキシマ・ケンタウリ", temp: 3042, lum: 0.0017 },
    { name: "ベテルギウス", temp: 3600, lum: 126000 },
    { name: "アルデバラン", temp: 3910, lum: 518 },
    { name: "プロキオンA", temp: 6530, lum: 6.9 },
    { name: "プロキオンB", temp: 7740, lum: 0.00049 },
    { name: "リゲル", temp: 12100, lum: 120000 },
    { name: "デネブ", temp: 8525, lum: 196000 },
    { name: "カノープス", temp: 7400, lum: 10700 },
    { name: "アケルナル", temp: 15000, lum: 3150 },
    { name: "アルタイル", temp: 7559, lum: 10.6 },
    { name: "αケンタウリA", temp: 5790, lum: 1.519 },
    { name: "バーナード星", temp: 3134, lum: 0.0035 },
    { name: "アルクトゥルス", temp: 4286, lum: 170 }
];

let currentScale = 1.0;

function updateAppScale() {
    const wrapper = document.getElementById('scale-wrapper');
    if (!wrapper) return;
    const baseWidth = 1600;
    const baseHeight = 900;
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight;
    
    currentScale = Math.min(windowWidth / baseWidth, windowHeight / baseHeight);
    wrapper.style.transform = `scale(${currentScale})`;
}


let currentVisualScale = 1.0;
let targetVisualScale = 1.0; 
let isVisualScaleInitialized = false;

let isCompareMode = false;
let currentVisualY = 0;
let targetVisualY = 0;
let currentMarkerScale = 1.0; 
let targetMarkerScale = 1.0;
let sunMesh; 

let sunLabel; // 【追加】太陽のラベル用グローバル変数

const hrConfig = {
    minTemp: 1000,
    maxTemp: 150000,
    minLum: 0.00001,
    maxLum: 100000000 
};

let hrZoom = 1.0;
let hrPanX = 0;
let hrPanY = 0;
let isDraggingHR = false;
let previousMousePosition = { x: 0, y: 0 };

let currentMarkerX = 0;
let currentMarkerY = 0;
let targetMarkerX = 0;
let targetMarkerY = 0;
let isMarkerPosInitialized = false;

let stellarData = null; 

let scene3D, camera3D, renderer3D, starMesh, controls3D;
let currentRealRadius = 1.0; 
let wdMesh; 

let raymarchScene, raymarchCamera, raymarchMaterial, raymarchQuad;
let isBlackHole = false; 

let isEventPlaying = false;     
let eventPhase = 0;             
let terminalEv = '';            
let bgRenderTarget;             

let pnTime = 0;                 
let isPlanetaryNebula = false;  
let pnQuad, pnMaterial;         

let snTime = 0;                 
let isSupernova = false;        
let snQuad, snMaterial;         

let nsRotationSpeed = 0.5;      
let nsGroup;                    
let previousEv = 'normal';      

let previousMass = -1;          
let previousRawPhase = -1;      
let initialRadius = null;       
let peakRadius = 0;             
let wasGiant = false;           
let wasLateBurn = false;        

let generalEventTimer = 0;      
let isGeneralEvent = false;     
let currentEventMsg = "水素核融合開始（主系列星）"; 

let targetZoom = 1.0;           
let isAutoZooming = false;      
let isPlaying = false;          

let lastActivePhase = 1.0;      

let sceneHR3D, cameraHR3D, rendererHR3D;
let markerMesh; 
let referenceStarMeshes = []; 
let referenceStarLabels = []; 

// ==========================================
// 2. 描画・計算用関数の定義
// ==========================================

function drawHRBackground() {
    const container = document.getElementById('hr-canvas-container');
    const canvas = document.getElementById('hr-bg-canvas');
    if (!container || !canvas) return;
    const ctx = canvas.getContext('2d');

    canvas.width = container.clientWidth;
    canvas.height = container.clientHeight;
    const width = canvas.width;
    const height = canvas.height;

    ctx.fillStyle = '#050505'; 
    ctx.fillRect(0, 0, width, height);

    ctx.globalCompositeOperation = 'screen';

    const drawGlow = (temp, lum, radiusX, radiusY, color, rotation = 0) => {
        const cx = getHrX(temp, width);
        const cy = getHrY(lum, height);
        
        ctx.save();
        ctx.translate(cx, cy);
        ctx.rotate(rotation);
        ctx.scale(1, radiusY / radiusX);

        const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, radiusX);
        gradient.addColorStop(0, color);
        gradient.addColorStop(1, 'rgba(0,0,0,0)');

        ctx.fillStyle = gradient;
        ctx.fillRect(-radiusX, -radiusX, radiusX * 2, radiusX * 2);
        ctx.restore();
    };

    const msStart = { x: getHrX(40000, width), y: getHrY(100000, height) };
    const msEnd = { x: getHrX(3000, width), y: getHrY(0.001, height) };
    const msAngle = Math.atan2(msEnd.y - msStart.y, msEnd.x - msStart.x);
    
    const zScale = (base) => base * hrZoom;

    drawGlow(20000, 1000, zScale(350), zScale(120), 'rgba(40, 80, 255, 0.5)', msAngle); 
    drawGlow(8000, 10, zScale(300), zScale(120), 'rgba(150, 200, 255, 0.4)', msAngle);   
    drawGlow(5000, 0.5, zScale(250), zScale(120), 'rgba(255, 255, 200, 0.4)', msAngle); 
    drawGlow(3000, 0.002, zScale(250), zScale(120), 'rgba(255, 80, 40, 0.5)', msAngle);  

    drawGlow(4000, 200, zScale(350), zScale(250), 'rgba(255, 120, 30, 0.4)'); 
    drawGlow(8000, 80000, zScale(500), zScale(250), 'rgba(255, 220, 150, 0.3)'); 
    
    const wdStart = { x: getHrX(30000, width), y: getHrY(0.1, height) };
    const wdEnd = { x: getHrX(5000, width), y: getHrY(0.00001, height) };
    const wdAngle = Math.atan2(wdEnd.y - wdStart.y, wdEnd.x - wdStart.x);
    drawGlow(15000, 0.001, zScale(350), zScale(120), 'rgba(80, 150, 255, 0.4)', wdAngle);
    
    ctx.globalCompositeOperation = 'source-over';

    ctx.strokeStyle = '#444444'; 
    ctx.lineWidth = 1;
    ctx.fillStyle = '#aaaaaa'; 
    ctx.font = '12px sans-serif';

    for (let exp = -20; exp <= 20; exp++) {
        const lum = Math.pow(10, exp);
        const y = getHrY(lum, height);
        
        if (y >= 0 && y <= height) {
            ctx.beginPath();
            ctx.moveTo(0, y);
            ctx.lineTo(width, y);
            ctx.stroke();

            if (y > 15 && y < height - 5) {
                ctx.fillText(`10^${exp}`, 5, y - 5);
            }
        }
    }

    const tempSteps = [];
    for (let exp = 1; exp <= 8; exp++) {
        const base = Math.pow(10, exp);
        tempSteps.push(base);
        tempSteps.push(base * 2.5);
        tempSteps.push(base * 5.0);
    }
    tempSteps.sort((a, b) => b - a);

    tempSteps.forEach(temp => {
        const x = getHrX(temp, width);
        
        if (x >= 0 && x <= width) {
            ctx.beginPath();
            ctx.moveTo(x, 0);
            ctx.lineTo(x, height);
            ctx.stroke();

            if (x > 10 && x < width - 10) {
                ctx.fillText(`${temp}K`, x + 5, height - 5);
            }
        }
    });

    ctx.fillStyle = '#dddddd'; 
    ctx.font = 'bold 14px sans-serif';
    ctx.textAlign = 'left';
    ctx.fillText('光度 (L/L☉)', 10, 20);
    ctx.textAlign = 'right';
    ctx.fillText('表面温度 (K)', width - 10, height - 20);

    ctx.save();
    ctx.strokeStyle = 'rgba(100, 255, 150, 0.3)'; 
    ctx.lineWidth = 1;
    ctx.setLineDash([5, 5]); 

    const radiusList = [10000, 1000, 100, 10, 1, 0.1, 0.01, 0.001, 0.0001];

    radiusList.forEach(R => {
        const calcLum = (t) => Math.pow(R, 2) * Math.pow(t / 5778, 4);
        
        const t1 = 200000;
        const l1 = calcLum(t1);
        const x1 = getHrX(t1, width);
        const y1 = getHrY(l1, height);

        const t2 = 500;
        const l2 = calcLum(t2);
        const x2 = getHrX(t2, width);
        const y2 = getHrY(l2, height);

        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.stroke();

        const m = (y2 - y1) / (x2 - x1);
        
        const padX = 60; 
        const padY = 40; 

        let yAtLeft = m * (padX - x1) + y1;
        let xAtTop = (padY - y1) / m + x1;
        let yAtRight = m * (width - padX - x1) + y1;
        let xAtBottom = (height - padY - y1) / m + x1;

        let labelX, labelY;
        let align = 'left';

        if (xAtTop >= padX && xAtTop <= width - padX) {
            labelX = xAtTop; labelY = padY; align = 'left';
        } else if (yAtLeft >= padY && yAtLeft <= height - padY) {
            labelX = padX; labelY = yAtLeft; align = 'left';
        } else if (xAtBottom >= padX && xAtBottom <= width - padX) {
            labelX = xAtBottom; labelY = height - padY; align = 'right';
        } else if (yAtRight >= padY && yAtRight <= height - padY) {
            labelX = width - padX; labelY = yAtRight; align = 'right';
        } else {
            return; 
        }

        const angle = Math.atan2(y2 - y1, x2 - x1);

        ctx.save();
        ctx.translate(labelX, labelY);
        ctx.rotate(angle);
        ctx.fillStyle = 'rgba(100, 255, 150, 0.8)';
        ctx.font = '11px sans-serif';
        ctx.textAlign = align;
        ctx.textBaseline = 'bottom';
        
        const offsetX = align === 'left' ? 5 : -5;
        ctx.fillText(`${R} R☉`, offsetX, -2);
        ctx.restore();
    });
    ctx.restore();

    const drawLabel = (text, temp, lum, color, angle = 0, fontSize = 24) => {
        const cx = getHrX(temp, width);
        const cy = getHrY(lum, height);

        ctx.save();
        ctx.translate(cx, cy);
        ctx.rotate(angle);
        ctx.fillStyle = color;
        ctx.font = `bold ${fontSize}px sans-serif`;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        
        ctx.shadowColor = '#000000';
        ctx.shadowBlur = 8;
        ctx.shadowOffsetX = 2;
        ctx.shadowOffsetY = 2;
        
        ctx.fillText(text, 0, 0);
        ctx.restore();
    };

    drawLabel("主系列星", 8000, 1, 'rgba(255, 255, 255, 0.8)', msAngle, 22);
    drawLabel("巨 星", 1800, 300, 'rgba(255, 200, 150, 0.9)', 0, 22);
    drawLabel("超巨星", 1800, 10000000, 'rgba(255, 230, 200, 0.9)', 0, 22);
    drawLabel("白色矮星", 15000, 0.001, 'rgba(150, 200, 255, 0.9)', wdAngle, 22);
}

function updateHR3DPositions() {
    const container = document.getElementById('hr-canvas-container');
    if (!container) return;
    const width = container.clientWidth;
    const height = container.clientHeight;

    referenceStarMeshes.forEach((mesh, index) => {
        const star = referenceStars[index];
        const x = getHrX(star.temp, width);
        const rawY = getHrY(star.lum, height); 
        const threeY = height - rawY;          
        
        mesh.position.x = x;
        mesh.position.y = threeY; 

        const label = referenceStarLabels[index];
        if (label) {
            label.style.left = `${x}px`;
            label.style.top = `${rawY}px`; 
            
            if (x < 0 || x > width || rawY < 0 || rawY > height) {
                label.style.display = 'none';
            } else {
                label.style.display = 'block';
            }
        }
    });
}

function getHrX(temp, width) {
    const logMax = Math.log10(hrConfig.maxTemp);
    const logMin = Math.log10(hrConfig.minTemp);
    const logTemp = Math.log10(temp);
    const baseX = ((logMax - logTemp) / (logMax - logMin)) * width;
    return (baseX - width / 2) * hrZoom + width / 2 + hrPanX;
}

function getHrY(lum, height) {
    const logMax = Math.log10(hrConfig.maxLum);
    const logMin = Math.log10(hrConfig.minLum);
    const logLum = Math.log10(lum);
    const baseY = height - (((logLum - logMin) / (logMax - logMin)) * height);
    return (baseY - height / 2) * hrZoom + height / 2 + hrPanY;
}

function setPlaying(play) {
    isPlaying = play;
    const playBtn = document.getElementById('play-btn');
    if (playBtn) {
        playBtn.innerText = isPlaying ? "II" : "▶";
    }
}

function createSpaceBackgroundTexture() {
    const canvas = document.createElement('canvas');
    canvas.width = 1024;
    canvas.height = 1024;
    const ctx = canvas.getContext('2d');

    ctx.fillStyle = '#020205';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    ctx.globalCompositeOperation = 'screen';
    for (let i = 0; i < 8; i++) {
        const x = Math.random() * canvas.width;
        const y = Math.random() * canvas.height;
        const radius = 200 + Math.random() * 400; 
        
        const grad = ctx.createRadialGradient(x, y, 0, x, y, radius);
        if (Math.random() > 0.5) {
            grad.addColorStop(0, 'rgba(60, 20, 100, 0.4)'); 
        } else {
            grad.addColorStop(0, 'rgba(20, 40, 120, 0.4)'); 
        }
        grad.addColorStop(1, 'rgba(0, 0, 0, 0)');
        
        ctx.fillStyle = grad;
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.fill();
    }

    ctx.globalCompositeOperation = 'source-over';
    const starColors = ['#ffffff', '#ddeeff', '#ffeedd', '#ffccaa', '#aaddff'];
    for (let i = 0; i < 800; i++) {
        const x = Math.random() * canvas.width;
        const y = Math.random() * canvas.height;
        
        const radius = Math.random() > 0.95 ? Math.random() * 1.5 + 1.0 : Math.random() * 0.8 + 0.2;
        const color = starColors[Math.floor(Math.random() * starColors.length)];
        
        if (radius > 1.0) {
            ctx.shadowColor = color;
            ctx.shadowBlur = 5;
        } else {
            ctx.shadowBlur = 0;
        }
        
        ctx.fillStyle = color;
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.fill();
    }

    const tex = new THREE.CanvasTexture(canvas);
    tex.wrapS = THREE.RepeatWrapping;
    tex.wrapT = THREE.RepeatWrapping;
    return tex;
}

function init3DViewer() {
    const container = document.getElementById('star-3d-container');
    if (!container) return;
    const width = container.clientWidth;
    const height = container.clientHeight;

    scene3D = new THREE.Scene();
    const aspect = width / height;
    const viewSize = 3;
    camera3D = new THREE.OrthographicCamera(-viewSize * aspect, viewSize * aspect, viewSize, -viewSize, 0.1, 10000);
    
    camera3D.position.set(0, 30, 100);
    camera3D.lookAt(0, 0, 0);

    renderer3D = new THREE.WebGLRenderer({ antialias: true }); 
    renderer3D.setSize(width, height);
    container.appendChild(renderer3D.domElement);

// 【修正】3Dビュー上に情報を表示するためのUIコンテナを作成
    const infoContainer = document.createElement('div');
    infoContainer.style.position = 'absolute';
    infoContainer.style.top = '10px';
    infoContainer.style.left = '10px';
    infoContainer.style.right = '10px';
    infoContainer.style.pointerEvents = 'none'; // マウス操作を透過
    infoContainer.style.color = '#ffffff';
    infoContainer.style.fontSize = '14px';
    infoContainer.style.fontFamily = 'sans-serif';
    infoContainer.style.textShadow = '1px 1px 2px #000';
    container.appendChild(infoContainer);

    const radiusLabel = document.createElement('div');
    radiusLabel.style.position = 'absolute';
    radiusLabel.style.right = '0';
    radiusLabel.id = 'info-radius';
    infoContainer.appendChild(radiusLabel);

    const massLabel = document.createElement('div');
    massLabel.style.position = 'absolute';
    massLabel.style.left = '0';
    massLabel.id = 'info-mass';
    infoContainer.appendChild(massLabel);


    if (typeof THREE.OrbitControls !== 'undefined') {
        controls3D = new THREE.OrbitControls(camera3D, renderer3D.domElement);
        controls3D.enablePan = false;
        controls3D.addEventListener('start', () => { isAutoZooming = false; });
    }

    const ambientLight = new THREE.AmbientLight(0x404040); 
    scene3D.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
    directionalLight.position.set(5, 3, 5);
    scene3D.add(directionalLight);

    scene3D.background = createSpaceBackgroundTexture();
    bgRenderTarget = new THREE.WebGLRenderTarget(width, height);

    const vertexShaderEl = document.getElementById('vertexShader');
    const fragmentShaderEl = document.getElementById('fragmentShader');
    const vShader = vertexShaderEl ? vertexShaderEl.textContent : '';
    const fShader = fragmentShaderEl ? fragmentShaderEl.textContent : '';

// === 太陽比較モード用の太陽メッシュ ===
    const sunGeo = new THREE.SphereGeometry(1.0, 32, 32); // 【修正】基準となる星と同じ半径1.0に変更
    
// 【修正】周辺減光を強化し、星に飲み込まれないよう「奥行き判定無視(depthTest: false)」を設定
    const sunMat = new THREE.ShaderMaterial({
        defines: { USE_ORTHOGRAPHIC: true },
        uniforms: { baseColor: { value: new THREE.Color(0xffaa00) } }, // 濃いオレンジ色ベース
        depthTest: false, // 星の内部に隠れないようにする
        transparent: true,
        vertexShader: vShader,
        fragmentShader: `
            uniform vec3 baseColor;
            varying vec3 vNormal;
            varying vec3 vViewPosition;
            void main() {
                vec3 normal = normalize(vNormal);
                vec3 viewDir = normalize(vViewPosition);
                float viewDot = max(dot(normal, viewDir), 0.0);
                
                // 本格的な周辺減光（中心は白黄色、縁はオレンジに）
                vec3 centerColor = vec3(1.0, 0.95, 0.8);
                vec3 edgeColor = baseColor;
                vec3 finalColor = mix(edgeColor, centerColor, pow(viewDot, 1.5));
                
                gl_FragColor = vec4(finalColor, 1.0);
            }
        `
    });
    sunMesh = new THREE.Mesh(sunGeo, sunMat);
    sunMesh.renderOrder = 999; // 強制的に最前面に描画
    sunMesh.visible = false;
    scene3D.add(sunMesh);


 // 【修正】画面の左下に太陽の表示スペースを空けて固定配置
    const sunLabelEl = document.createElement('div');
    sunLabelEl.id = 'sun-size-label';
    sunLabelEl.innerText = '太陽の大きさ';
    sunLabelEl.style.position = 'absolute';
    sunLabelEl.style.bottom = '10px'; // 太陽の表示スペースを考慮した下部余白
    sunLabelEl.style.left = '40px';  // 太陽の表示スペースを考慮した左側余白（あとで微調整してください）
    sunLabelEl.style.color = '#ffffff';
    sunLabelEl.style.fontSize = '12px'; // 太陽の表示　大きさ
    sunLabelEl.style.fontWeight = 'bold';
    sunLabelEl.style.textShadow = '1px 1px 2px #000, -1px -1px 2px #000, 1px -1px 2px #000, -1px 1px 2px #000';
    sunLabelEl.style.pointerEvents = 'none';
    sunLabelEl.style.display = 'none'; // 初期状態は非表示
    sunLabelEl.style.zIndex = '1000'; 
    container.appendChild(sunLabelEl);
    sunLabel = sunLabelEl;




    const geometry = new THREE.SphereGeometry(1, 64, 64); 

    const customMaterial = new THREE.ShaderMaterial({
        defines: { USE_ORDINARY: false, USE_ORTHOGRAPHIC: true },
        uniforms: {
            time: { value: 0.0 },
            baseColor: { value: new THREE.Color(0xffff00) },
            noiseScale: { value: 20.0 },
            sunspotSize: { value: 2.0 },
            sunspotIntensity: { value: 0.0 },
            sunspotThreshold: { value: 0.85 }
        },
        vertexShader: vShader,
        fragmentShader: fShader
    });
    starMesh = new THREE.Mesh(geometry, customMaterial);

    const haloCanvas = document.createElement('canvas');
    haloCanvas.width = 128; haloCanvas.height = 128;
    const ctx = haloCanvas.getContext('2d');
    const grad = ctx.createRadialGradient(64, 64, 0, 64, 64, 64);

    grad.addColorStop(0, 'rgba(255, 255, 255, 1.0)');
    grad.addColorStop(0.4, 'rgba(255, 255, 255, 0.5)');
    grad.addColorStop(1, 'rgba(255, 255, 255, 0)');

    ctx.fillStyle = grad; ctx.fillRect(0, 0, 128, 128);
    const coronaTexture = new THREE.CanvasTexture(haloCanvas);
    
    const coronaMaterial = new THREE.SpriteMaterial({
        map: coronaTexture, blending: THREE.AdditiveBlending, depthWrite: false
    });
    const corona = new THREE.Sprite(coronaMaterial);
    corona.scale.set(4.0, 4.0, 1);
    starMesh.add(corona); 

    scene3D.add(starMesh);

    const wdMaterial = new THREE.ShaderMaterial({
        defines: { USE_ORTHOGRAPHIC: true },
        uniforms: { baseColor: { value: new THREE.Color(0xffffff) } },
        vertexShader: `
            varying vec3 vNormal;
            varying vec3 vViewPosition;
            void main() {
                vNormal = normalize(normalMatrix * normal);
                vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
                vViewPosition = vec3(0.0, 0.0, 1.0);
                gl_Position = projectionMatrix * mvPosition;
            }
        `,
        fragmentShader: `
            uniform vec3 baseColor;
            varying vec3 vNormal;
            varying vec3 vViewPosition;
            void main() {
                vec3 normal = normalize(vNormal);
                vec3 viewDir = normalize(vViewPosition);
                float viewDot = max(dot(normal, viewDir), 0.0);
                float limbFactor = 0.3 + 0.7 * pow(viewDot, 1.5);
                gl_FragColor = vec4(baseColor * limbFactor, 1.0);
            }
        `
    });
    wdMesh = new THREE.Mesh(geometry, wdMaterial);
    wdMesh.visible = false;
    scene3D.add(wdMesh);

    nsGroup = new THREE.Group();
    nsGroup.visible = false;

    const nsMaterial = new THREE.ShaderMaterial({
        defines: { USE_ORTHOGRAPHIC: true },
        uniforms: { baseColor: { value: new THREE.Color(0xffffff) } },
        vertexShader: `
            varying vec3 vNormal;
            varying vec3 vViewPosition;
            void main() {
                vNormal = normalize(normalMatrix * normal);
                vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
                vViewPosition = vec3(0.0, 0.0, 1.0);
                gl_Position = projectionMatrix * mvPosition;
            }
        `,
        fragmentShader: `
            uniform vec3 baseColor;
            varying vec3 vNormal;
            varying vec3 vViewPosition;
            void main() {
                vec3 normal = normalize(vNormal);
                vec3 viewDir = normalize(vViewPosition);
                float viewDot = max(dot(normal, viewDir), 0.0);
                float limbFactor = 0.2 + 0.8 * pow(viewDot, 2.0);
                gl_FragColor = vec4(baseColor * limbFactor, 1.0);
            }
        `
    });

    const nsStarMesh = new THREE.Mesh(geometry, nsMaterial);
    
    nsGroup.add(nsStarMesh);
    const jetRotationGroup = new THREE.Group();
    jetRotationGroup.rotation.z = Math.PI / 16;
    nsGroup.add(jetRotationGroup);
    const jetMat = new THREE.ShaderMaterial({
        transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide,
        fragmentShader: `
            varying vec2 vUv;
            void main() {
                float dist = abs(vUv.x - 0.5) * 2.0;
                float edge = pow(1.0 - dist, 8.0);
                float alpha = (1.0 - vUv.y) * edge;
                gl_FragColor = vec4(0.5, 0.8, 1.0, alpha * 4.0);
            }
        `,
        vertexShader: `
            varying vec2 vUv;
            void main() {
                vUv = uv;
                gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
            }
        `
    });
    const jetGeom = new THREE.CylinderGeometry(1.5, 0.05, 15, 32);
    const jetTop = new THREE.Mesh(jetGeom, jetMat);
    jetTop.position.set(0, 8.5, 0);
    jetRotationGroup.add(jetTop);
    const jetBot = new THREE.Mesh(jetGeom, jetMat);
    jetBot.position.set(0, -8.5, 0);
    jetBot.rotation.x = Math.PI;
    jetRotationGroup.add(jetBot);
    scene3D.add(nsGroup);

    snMaterial = new THREE.ShaderMaterial({
        transparent: true, blending: THREE.AdditiveBlending, depthWrite: false,
        uniforms: { time: { value: 0.0 }, snTime: { value: 0.0 }, aspectRatio: { value: width / height } },
        vertexShader: `
            varying vec2 vUv;
            void main() { vUv = uv; gl_Position = vec4(position.xy, 0.999, 1.0); }
        `,
        fragmentShader: `
            varying vec2 vUv; uniform float time; uniform float snTime; uniform float aspectRatio;
            float hash(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }
            float noise(vec2 p) {
                vec2 i = floor(p); vec2 f = fract(p); vec2 u = f * f * (3.0 - 2.0 * f);
                return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x), mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y);
            }
            float fbm(vec2 p) { float v = 0.0; float a = 0.5; for (int i=0; i<4; i++) { v += a * noise(p); p *= 2.0; a *= 0.5; } return v; }
            void main() {
                vec2 uv = (vUv - 0.5) * 2.0; uv.x *= aspectRatio; float dist = length(uv);
                float flashSize = smoothstep(0.0, 0.15, snTime) * 4.0;
                float flashAlpha = 1.0 - smoothstep(0.15, 0.4, snTime);
                float flash = smoothstep(flashSize, flashSize * 0.1, dist) * flashAlpha;
                vec3 flashColor = vec3(1.0, 0.95, 0.9) * flash * 2.0;
                float expansion = smoothstep(0.1, 1.0, snTime);
                float cloudScale = mix(0.1, 3.0, expansion); vec2 cloudUv = uv / cloudScale;
                float t = time * 0.1; float n1 = fbm(cloudUv * 3.0 - vec2(t, t)); float n2 = fbm(cloudUv * 5.0 + vec2(t*0.8, -t*0.5));
                float density = n1 * n2;
                float ring = smoothstep(1.0, 0.4, dist / cloudScale) * smoothstep(0.0, 0.3, dist / cloudScale);
                float cloudAlpha = density * ring * smoothstep(0.15, 0.5, snTime);
                vec3 cloudColor = mix(vec3(1.0, 0.3, 0.1), vec3(0.2, 0.5, 1.0), n2) * cloudAlpha * 3.0;
                gl_FragColor = vec4(flashColor + cloudColor, flashAlpha + cloudAlpha);
            }
        `
    });
    snQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), snMaterial);
    snQuad.renderOrder = -1;
    snQuad.visible = false;
    scene3D.add(snQuad);

    pnMaterial = new THREE.ShaderMaterial({
        transparent: true, blending: THREE.AdditiveBlending, depthWrite: false,
        uniforms: { time: { value: 0.0 }, pnTime: { value: 0.0 }, aspectRatio: { value: width / height } },
        vertexShader: `
            varying vec2 vUv;
            void main() { vUv = uv; gl_Position = vec4(position.xy, 0.998, 1.0); }
        `,
        fragmentShader: `
            varying vec2 vUv; uniform float time; uniform float pnTime; uniform float aspectRatio;
            float hash(vec2 p) { return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); }
            float noise(vec2 p) {
                vec2 i = floor(p); vec2 f = fract(p); vec2 u = f * f * (3.0 - 2.0 * f);
                return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x), mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y);
            }
            float fbm(vec2 p) { float v = 0.0; float a = 0.5; for (int i=0; i<4; i++) { v += a * noise(p); p *= 2.0; a *= 0.5; } return v; }
            void main() {
                vec2 uv = (vUv - 0.5) * 2.0; 
                uv.x *= aspectRatio;
                uv.y *= 1.3; 
                float scale = mix(0.1, 3.0, smoothstep(0.0, 1.0, pnTime));
                vec2 scaledUv = uv / scale;
                float dist = length(scaledUv);
                float ring = smoothstep(0.2, 0.5, dist) * smoothstep(1.0, 0.7, dist);
                float n = fbm(scaledUv * 2.5 + time * 0.05);
                float density = ring * n;
                vec3 colorOuter = vec3(0.9, 0.2, 0.5);
                vec3 colorInner = vec3(0.1, 0.8, 0.9);
                vec3 color = mix(colorInner, colorOuter, dist);
                float alpha = density * smoothstep(0.0, 0.2, pnTime) * (1.0 - smoothstep(0.7, 1.0, pnTime));
                gl_FragColor = vec4(color * alpha * 2.5, alpha);
            }
        `
    });
    pnQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), pnMaterial);
    pnQuad.renderOrder = -2;
    pnQuad.visible = false;
    scene3D.add(pnQuad);

    raymarchScene = new THREE.Scene();
    raymarchCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    
    raymarchMaterial = new THREE.ShaderMaterial({
        uniforms: {
            time: { value: 0.0 }, resolution: { value: new THREE.Vector2(width, height) },
            cameraPos: { value: new THREE.Vector3() }, cameraDir: { value: new THREE.Vector3() },
            cameraUp: { value: new THREE.Vector3() }, orthoScale: { value: 3.0 },
            bhScale: { value: 1.0 }, bgTexture: { value: scene3D.background }
        },
        vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 1.0); }`,
        fragmentShader: `
            varying vec2 vUv; uniform float time; uniform vec2 resolution; uniform vec3 cameraPos;
            uniform vec3 cameraDir; uniform vec3 cameraUp; uniform float orthoScale; uniform float bhScale; uniform sampler2D bgTexture;
            vec4 getDiskColor(vec3 pos) {
                float dist = length(pos.xz); float inner = 1.8; float outer = 5.0;
                if(dist < inner || dist > outer) return vec4(0.0);
                float r = clamp((dist - inner) / (outer - inner), 0.0, 1.0);
                float angle = atan(pos.z, pos.x); vec2 uv = vec2(r, angle);
                float noise = fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453);
                float t = time * 0.5; float stripe1 = (sin(r * 40.0 - angle * 2.0 - t * 2.0) + 1.0) * 0.5;
                float stripe2 = (sin(r * 25.0 + angle * 3.0 + t) + 1.0) * 0.5;
                float gasDensity = (stripe1 * 0.6 + stripe2 * 0.4) * (0.8 + noise * 0.2);
                vec3 baseColor = mix(vec3(1.0, 0.95, 0.8), vec3(0.9, 0.4, 0.1), pow(r, 0.8));
                float alpha = smoothstep(0.0, 0.05, r) * smoothstep(1.0, 0.8, r) * (0.6 + 0.4 * gasDensity);
                return vec4(baseColor * (0.7 + 0.3 * gasDensity), alpha);
            }
            void main() {
                vec2 p = (vUv - 0.5) * 2.0; vec3 right = normalize(cross(cameraDir, cameraUp)); vec3 trueUp = normalize(cross(right, cameraDir));
                float aspect = resolution.x / resolution.y;
                vec3 worldRayPos = cameraPos + right * p.x * orthoScale * aspect + trueUp * p.y * orthoScale;
                vec3 rayPos = worldRayPos / bhScale; vec3 rayDir = normalize(cameraDir);
                vec3 color = vec3(0.0); float alphaAcc = 0.0; bool hitBH = false;
                vec3 v = rayDir; float R_bound = 20.0; 
                float b = 2.0 * dot(rayPos, rayDir); float c = dot(rayPos, rayPos) - R_bound * R_bound; float det = b*b - 4.0 * c;
                if (det > 0.0) { float t_intersect = (-b - sqrt(det)) / 2.0; if (t_intersect > 0.0) rayPos += rayDir * t_intersect; }
                if (det >= 0.0) {
                    float dt = 0.05;
                    for(int i = 0; i < 200; i++) {
                        float r = length(rayPos);
                        if(r < 1.0) { hitBH = true; break; }
                        if(r > R_bound + 1.0) break; 
                        vec3 accel = -1.5 * rayPos / (r * r * r * r * r); v = normalize(v + accel * dt);
                        vec3 nextPos = rayPos + v * dt;
                        if (rayPos.y * nextPos.y <= 0.0 && abs(v.y) > 0.00001) {
                            float t_disk = -rayPos.y / v.y; vec3 hitPos = rayPos + v * t_disk;
                            vec4 diskCol = getDiskColor(hitPos);
                            if(diskCol.a > 0.0) { color += diskCol.rgb * diskCol.a * (1.0 - alphaAcc); alphaAcc += diskCol.a * (1.0 - alphaAcc); }
                        }
                        rayPos = nextPos; dt = 0.015 + 0.05 * (r - 1.0); 
                    }
                }
                vec3 bg;
                if (hitBH) bg = vec3(0.0);
                else {
                    vec3 deltaV = v - normalize(cameraDir); float dx = dot(deltaV, right); float dy = dot(deltaV, trueUp);
                    bg = texture2D(bgTexture, vUv + vec2(dx, dy) * 1.5).rgb;
                }
                gl_FragColor = vec4(color + bg * (1.0 - alphaAcc), 1.0);
            }
        `
    });
    raymarchQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), raymarchMaterial);
    raymarchScene.add(raymarchQuad);

    animate3D();
}

function initHR3DViewer() {
    const container = document.getElementById('hr-3d-container');
    if (!container) return;
    const width = container.clientWidth;
    const height = container.clientHeight;

    sceneHR3D = new THREE.Scene();
    
    // 【完全修正】前回のクラッシュ原因であったタイポとコメントを完全に排除しました
    cameraHR3D = new THREE.OrthographicCamera(0, width, height, 0, 0.1, 2000);
    cameraHR3D.position.z = 1000; 

    rendererHR3D = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    rendererHR3D.setSize(width, height);
    container.appendChild(rendererHR3D.domElement);

    const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
    sceneHR3D.add(ambientLight);

    const baseGeometry = new THREE.SphereGeometry(1, 32, 32);
    const labelsContainer = document.getElementById('hr-labels-container');
    
    const vertexShaderEl = document.getElementById('vertexShader');
    const fragmentShaderEl = document.getElementById('fragmentShader');
    const vShader = vertexShaderEl ? vertexShaderEl.textContent : '';
    const fShader = fragmentShaderEl ? fragmentShaderEl.textContent : '';

    referenceStars.forEach(star => {
        const material = new THREE.ShaderMaterial({
            defines: {
                USE_ORDINARY: false, 
                USE_ORTHOGRAPHIC: true
            },
            uniforms: {
                time: { value: Math.random() * 100 }, 
                baseColor: { value: temperatureToColor(star.temp) },
                noiseScale: { value: 15.0 } 
            },
            vertexShader: vShader,
            fragmentShader: fShader
        });
                
        const mesh = new THREE.Mesh(baseGeometry, material);
        
        mesh.position.x = getHrX(star.temp, width);
        mesh.position.y = height - getHrY(star.lum, height);
        mesh.position.z = 0; 

        const radius = Math.pow(Math.abs(star.lum) / Math.pow(star.temp/5778, 4), 0.5);
        const scale = getDisplayRadius(radius) * 8; 
        mesh.scale.set(scale, scale, scale);

        material.uniforms.noiseScale.value = calculateNoiseScale(radius);

        sceneHR3D.add(mesh);
        referenceStarMeshes.push(mesh);

        if (labelsContainer) {
            const label = document.createElement('div');
            label.className = 'star-label';
            label.innerText = star.name;
            labelsContainer.appendChild(label);
            referenceStarLabels.push(label);
        }
    });

    const markerMaterial = new THREE.ShaderMaterial({
        defines: {
            USE_ORDINARY: false, 
            USE_ORTHOGRAPHIC: true
        },
        uniforms: {
            time: { value: 0.0 },
            baseColor: { value: new THREE.Color(0xffffff) },
            noiseScale: { value: 20.0 }
        },
        vertexShader: vShader,
        fragmentShader: fShader
    });
    markerMesh = new THREE.Mesh(baseGeometry, markerMaterial);
    markerMesh.position.z = 500; 

    const haloCanvas = document.createElement('canvas');
    haloCanvas.width = 128;
    haloCanvas.height = 128;
    const ctx = haloCanvas.getContext('2d');
    const grad = ctx.createRadialGradient(64, 64, 0, 64, 64, 64);
    grad.addColorStop(0, 'rgba(255, 255, 255, 0.8)');
    grad.addColorStop(0.5, 'rgba(255, 255, 255, 0.3)');
    grad.addColorStop(1, 'rgba(255, 255, 255, 0)');
    ctx.fillStyle = grad; 
    ctx.fillRect(0, 0, 128, 128);
    const haloTexture = new THREE.CanvasTexture(haloCanvas);

    const haloSpriteMaterial = new THREE.SpriteMaterial({
        map: haloTexture,
        color: 0xffffff, 
        transparent: true,
        blending: THREE.AdditiveBlending,
        depthWrite: false
    });
    const markerHalo = new THREE.Sprite(haloSpriteMaterial);
    markerHalo.scale.set(4.5, 4.5, 1); 
    markerMesh.halo = markerHalo; 
    markerMesh.add(markerHalo);

    sceneHR3D.add(markerMesh);

    const dirLight = new THREE.DirectionalLight(0xffffff, 1);
    dirLight.position.set(0, 0, 1000);
    sceneHR3D.add(dirLight);
}

function animate3D() {
    requestAnimationFrame(animate3D);
    
    if (isAutoZooming && camera3D) {
        if (Math.abs(camera3D.zoom - targetZoom) > 0.01) {
            camera3D.zoom += (targetZoom - camera3D.zoom) * 0.05;
            camera3D.updateProjectionMatrix();
        } else {
            isAutoZooming = false;
        }
    }

    if (isPlaying && !isEventPlaying) {
        const phaseSlider = document.getElementById('phase-slider');
        if (phaseSlider) {
            let currentPhase = parseFloat(phaseSlider.value);
            if (currentPhase < 1.0) {
                currentPhase += 0.0015; 
                if (currentPhase > 1.0) currentPhase = 1.0;
                phaseSlider.value = currentPhase;
                updateSimulation();
            } else {
                setPlaying(false); 
            }
        }
    }

    currentVisualScale += (targetVisualScale - currentVisualScale) * 0.05;
    currentVisualY += (targetVisualY - currentVisualY) * 0.05;
    currentMarkerScale += (targetMarkerScale - currentMarkerScale) * 0.05;

// === 【修正】比較モード時の太陽表示制御と位置更新 ===
    // 1. まず表示するかどうかを正しく判定
    let showSun = false;
    if (isCompareMode) {
        showSun = !isPlanetaryNebula && !isSupernova;
    }
    
    if (sunMesh) sunMesh.visible = showSun;
    // DOM要素の制御は visible ではなく style.display を使用
    if (sunLabel) sunLabel.style.display = showSun ? 'block' : 'none';

    // 2. 表示されている場合のみ、ズーム値に連動させて確実な位置に配置（ラベルは左下固定のため追従計算を削除）
    if (showSun && sunMesh && camera3D) {
        const aspect = camera3D.right / camera3D.top;
        // ズームインしても画面の左下隅から逃げないように camera3D.zoom で割る
        const viewSize = camera3D.top / camera3D.zoom; 
        const padding = 1.0 / camera3D.zoom; 

        if (camera3D.zoom > 10.0) {
            // 終末天体との比較時：Yは中央、右端が画面左端から1/5の位置に来るように配置
            const viewWidth = viewSize * aspect;
            sunMesh.position.set(-viewWidth * 0.6 - 1.0, 0, 0); // 【修正】半径1.0に合わせてオフセットを変更
        } else {
            // 通常時：画面の左下隅に配置
            sunMesh.position.set(-viewSize * aspect + padding, -viewSize + padding, 0);
        }
    }

    // 星本体の更新（そのまま）
    if (starMesh && starMesh.visible) {
        starMesh.rotation.y += 0.005;
        starMesh.material.uniforms.time.value += 0.05;
        starMesh.scale.set(currentVisualScale, currentVisualScale, currentVisualScale);
        starMesh.position.y = currentVisualY;
    }
    if (wdMesh && wdMesh.visible) {
        wdMesh.scale.set(currentVisualScale, currentVisualScale, currentVisualScale);
        wdMesh.position.y = currentVisualY;
    }
    if (nsGroup && nsGroup.visible) {
        nsGroup.scale.set(currentVisualScale, currentVisualScale, currentVisualScale);
        nsGroup.position.y = currentVisualY;
    }




    if (markerMesh) {
        markerMesh.rotation.y += 0.01;
        markerMesh.material.uniforms.time.value += 0.05;
        
        if (isDraggingHR) {
            currentMarkerX = targetMarkerX;
            currentMarkerY = targetMarkerY;
        } else {
            currentMarkerX += (targetMarkerX - currentMarkerX) * 0.05;
            currentMarkerY += (targetMarkerY - currentMarkerY) * 0.05;
        }
        
        markerMesh.position.x = currentMarkerX;
        markerMesh.position.y = currentMarkerY;

        const displayScaleMarker = currentMarkerScale * 8;
        markerMesh.scale.set(displayScaleMarker, displayScaleMarker, displayScaleMarker);

        if (markerMesh.halo) {
            const fringeWidth = 25.0; 
            const haloScale = 2.5 + (fringeWidth / displayScaleMarker); 
            markerMesh.halo.scale.set(haloScale, haloScale, 1);
        }
    }

    referenceStarMeshes.forEach(mesh => {
        mesh.rotation.y += 0.005;
        mesh.material.uniforms.time.value += 0.05;
    });

    if (nsGroup && nsGroup.visible) {
        nsGroup.children[1].rotation.y += nsRotationSpeed;
    }

    if (isGeneralEvent) {
        if (generalEventTimer < 1.5) { 
            generalEventTimer += 0.015; 
        } else {
            isGeneralEvent = false;
            if (isEventPlaying) {
                isEventPlaying = false;
                const pSlider = document.getElementById('phase-slider');
                const mSlider = document.getElementById('mass-slider');
                const zSlider = document.getElementById('metallicity-slider');
                if(pSlider) pSlider.disabled = false;
                if(mSlider) mSlider.disabled = false;
                if(zSlider) zSlider.disabled = false;
            }
            updateSimulation();
        }
    }

    if (isPlanetaryNebula) {
        if (pnTime < 1.5) { 
            pnTime += 0.005; 
        } else {
            isPlanetaryNebula = false;
            if (isEventPlaying) {
                isEventPlaying = false;
                const pSlider = document.getElementById('phase-slider');
                const mSlider = document.getElementById('mass-slider');
                const zSlider = document.getElementById('metallicity-slider');
                if(pSlider) pSlider.disabled = false;
                if(mSlider) mSlider.disabled = false;
                if(zSlider) zSlider.disabled = false;
            }
            updateSimulation();
        }
        if (pnMaterial) {
            pnMaterial.uniforms.time.value += 0.05; 
            pnMaterial.uniforms.pnTime.value = pnTime;
        }
    }

    if (isSupernova) {
        if (snTime < 1.5) { 
            snTime += 0.005; 
            if (snTime > 0.4) {
                if (terminalEv === 'bh') isBlackHole = true;
                else if (terminalEv === 'ns' && nsGroup) nsGroup.visible = true;
            }
        } else {
            isSupernova = false;
            if (isEventPlaying) {
                isEventPlaying = false;
                const pSlider = document.getElementById('phase-slider');
                const mSlider = document.getElementById('mass-slider');
                const zSlider = document.getElementById('metallicity-slider');
                if(pSlider) pSlider.disabled = false;
                if(mSlider) mSlider.disabled = false;
                if(zSlider) zSlider.disabled = false;
            }
            updateSimulation();
        }
        if (snMaterial) {
            snMaterial.uniforms.time.value += 0.05; 
            snMaterial.uniforms.snTime.value = snTime;
        }
    }

    if (controls3D) controls3D.update();
    updateScaleBar(); 



    if (isBlackHole && camera3D && raymarchMaterial) {
        raymarchMaterial.uniforms.time.value += 0.05;


        const visibleHeight = (camera3D.top - camera3D.bottom) / camera3D.zoom;
        raymarchMaterial.uniforms.orthoScale.value = visibleHeight / 2.0;
        
        const bhScale = raymarchMaterial.uniforms.bhScale.value;
        const safeDist = bhScale * 50.0;
        
        if (isCompareMode) {
            // 【修正】比較モード時のみ：常に「斜め上から見下ろす」理想的なアングルに固定
            const fakePos = new THREE.Vector3(0, 30, 100).normalize().multiplyScalar(safeDist);
            raymarchMaterial.uniforms.cameraPos.value.copy(fakePos);
            
            const fakeDir = new THREE.Vector3(0, 0, 0).sub(fakePos).normalize();
            raymarchMaterial.uniforms.cameraDir.value.copy(fakeDir);
            
            raymarchMaterial.uniforms.cameraUp.value.set(0, 1, 0);
        } else {
            // 【修正】ズームモード時：マウス操作を反映しつつ、精度落ちを防ぐため距離だけ偽装
            const camDir = camera3D.position.clone().normalize();
            const fakePos = camDir.multiplyScalar(safeDist);
            raymarchMaterial.uniforms.cameraPos.value.copy(fakePos);
            
            const dir = new THREE.Vector3();
            camera3D.getWorldDirection(dir);
            raymarchMaterial.uniforms.cameraDir.value.copy(dir);
            
            raymarchMaterial.uniforms.cameraUp.value.copy(camera3D.up);
        }

        const container = document.getElementById('star-3d-container');
        if (container) {
            raymarchMaterial.uniforms.resolution.value.set(container.clientWidth, container.clientHeight);
        }
    }

    if (isBlackHole) {
        if (bgRenderTarget && renderer3D && scene3D && camera3D) {
// 1. 太陽とラベルを一時的に非表示にする
            const wasSunVisible = sunMesh ? sunMesh.visible : false;
            // BH描画時も style.display で状態を取得
            const wasLabelVisible = sunLabel ? (sunLabel.style.display !== 'none') : false;
            
            if (sunMesh) sunMesh.visible = false;
            if (sunLabel) sunLabel.style.display = 'none';

            // 2. 背景（太陽なし）をレンダリングしてテクスチャに焼き付ける
            renderer3D.setRenderTarget(bgRenderTarget);
            renderer3D.render(scene3D, camera3D);
            if (raymarchMaterial) raymarchMaterial.uniforms.bgTexture.value = bgRenderTarget.texture;

            // 3. 画面全体にブラックホールを描画（宇宙の背景だけが歪む）
            if (raymarchScene && raymarchCamera) {
                renderer3D.setRenderTarget(null);
                renderer3D.render(raymarchScene, raymarchCamera);
            }

            // 4. 太陽とラベルの表示状態を元に戻す
            if (sunMesh) sunMesh.visible = wasSunVisible;
            if (sunLabel) sunLabel.style.display = wasLabelVisible ? 'block' : 'none';

            // 5. 太陽またはラベルが存在する場合は、BHの手前に重ねて上書き描画する
            if (wasSunVisible || wasLabelVisible) {
                // 星空の背景まで描画されるとBHが隠れるため、一時的に背景を無効化
                const oldBg = scene3D.background;
                scene3D.background = null; 
                
                // 画面をクリアせずに上から重ね描きする
                renderer3D.autoClear = false;
                renderer3D.render(scene3D, camera3D);
                renderer3D.autoClear = true; // 描画後に設定を戻す
                
                scene3D.background = oldBg; // 背景を元に戻す
            }
        }
    } else {
        if (renderer3D && scene3D && camera3D) {
            renderer3D.setRenderTarget(null);
            renderer3D.render(scene3D, camera3D);
        }
    }




    if (rendererHR3D && sceneHR3D && cameraHR3D) {
        rendererHR3D.render(sceneHR3D, cameraHR3D);
    }
}

function updateScaleBar() {
    const scaleBarContainer = document.getElementById('scale-bar-container');
    
    if (isCompareMode) {
        if (scaleBarContainer) scaleBarContainer.style.display = 'none';
        return;
    } else {
        if (scaleBarContainer) scaleBarContainer.style.display = 'block';
    }

    if (!camera3D || !renderer3D) return;
    
    const container = document.getElementById('star-3d-container');
    if (!container) return;

    const width = container.clientWidth;
    if (width === 0) return;

    const visibleWidth = (camera3D.right - camera3D.left) / camera3D.zoom;
    
    const scaleBarPixels = 100;
    const unitsInScaleBar = (scaleBarPixels / width) * visibleWidth;
    
    const displayRadius = getDisplayRadius(currentRealRadius);
    if (displayRadius === 0) return;

    const realUnitsPerDisplayUnit = currentRealRadius / displayRadius;
    const realValue = unitsInScaleBar * realUnitsPerDisplayUnit;
    
    let displayValue = realValue;
    let formattedValue;
    if (displayValue >= 10) {
        formattedValue = Math.round(displayValue);
    } else if (displayValue >= 1) {
        formattedValue = displayValue.toFixed(1);
    } else {
        formattedValue = displayValue.toPrecision(2);
    }
    
    const textEl = document.getElementById('scale-bar-text');
    if (textEl) {
        textEl.innerText = `${formattedValue} R☉`;
    }

// 【追加】独立した小さな文字として「1R☉=696,000km」を右側に配置
    if (scaleBarContainer) {
        let unitEl = document.getElementById('scale-unit-text');
        if (!unitEl) {
            unitEl = document.createElement('div');
            unitEl.id = 'scale-unit-text';
            // スケールバー枠の右端からさらに右へ配置
            unitEl.style.position = 'absolute';
            unitEl.style.left = '100%'; 
            unitEl.style.bottom = '0px'; 
            unitEl.style.marginLeft = '15px'; 
            unitEl.style.whiteSpace = 'nowrap'; 
            unitEl.style.fontSize = '12px'; 
            unitEl.style.color = '#cccccc'; 
            unitEl.style.textShadow = '1px 1px 2px #000, -1px -1px 2px #000, 1px -1px 2px #000, -1px 1px 2px #000';
            scaleBarContainer.appendChild(unitEl);
        }
        unitEl.innerText = '1R☉=696,000km';
    }



}



function updateSimulation() {
    if (!stellarData) return;

    const phaseSlider = document.getElementById('phase-slider');
    const massSlider = document.getElementById('mass-slider');
    const zSlider = document.getElementById('metallicity-slider');
    const phaseValueEl = document.getElementById('phase-value');
    const massValueEl = document.getElementById('mass-value');
    const zValueEl = document.getElementById('metallicity-value');

    if (!phaseSlider || !massSlider || !zSlider) return;

    if (isEventPlaying) {
        phaseSlider.value = eventPhase;
        if (phaseValueEl) phaseValueEl.innerText = eventPhase.toFixed(3);
        const eventEl = document.getElementById('event-value');
        if(eventEl) eventEl.innerText = currentEventMsg;
        return; 
    }

    const mass = Math.pow(10, parseFloat(massSlider.value));
    const z = parseFloat(zSlider.value);
    const rawPhase = parseFloat(phaseSlider.value);
    
    const isForward = rawPhase > previousRawPhase;
    const isBackward = rawPhase < previousRawPhase;
    previousRawPhase = rawPhase;

    if (mass !== previousMass) {
        const initProps = calculateEvolutionFromData(mass, z, 0);
        initialRadius = initProps.radius;
        peakRadius = 0;
        wasGiant = false;
        wasLateBurn = false;
        previousMass = mass;
        targetZoom = 1.0; 
        isAutoZooming = true; 
        setPlaying(false);    

        lastActivePhase = 1.0;
        for (let p = 0.0; p <= 1.0; p += 0.01) {
            let props = calculateEvolutionFromData(mass, z, p);
            if (props.ev === 'wd' || props.ev === 'sn' || props.ev === 'ns' || props.ev === 'bh') {
                lastActivePhase = Math.max(0, p - 0.01);
                break;
            }
        }
    }

    let mappedPhase;
    if (rawPhase >= 1.0) {
        mappedPhase = 1.0; 
    } else if (rawPhase < 0.7) {
        mappedPhase = (rawPhase / 0.7) * (lastActivePhase * 0.8);
    } else {
        const ratio = (rawPhase - 0.7) / 0.3;
        mappedPhase = (lastActivePhase * 0.8) + ratio * (lastActivePhase * 0.2);
    }

    if (massValueEl) massValueEl.innerText = mass.toFixed(2);
    if (zValueEl) zValueEl.innerText = z.toFixed(3); 
    if (phaseValueEl) phaseValueEl.innerText = rawPhase.toFixed(3);

    const starProps = calculateEvolutionFromData(mass, z, mappedPhase);
    currentRealRadius = starProps.radius;

    if (isBackward) {
        peakRadius = starProps.radius;
    } else if (starProps.radius > peakRadius) {
        peakRadius = starProps.radius;
    }

    const isGiantNow = starProps.radius > initialRadius * 10;
    const isLateBurnNow = isGiantNow && (starProps.radius < peakRadius * 0.9);
    const isHighMass = mass >= 8.0;
    const isEndState = (ev) => ev === 'sn' || ev === 'ns' || ev === 'bh' || ev === 'wd';

    let triggerEvent = false;
    let eventType = "";

    if (isForward) {
        if (!wasGiant && isGiantNow && !isEndState(starProps.ev)) {
            triggerEvent = true;
            eventType = 'giant';
            currentEventMsg = isHighMass ? "水素殻燃焼開始（赤色超巨星へ）" : "水素殻燃焼開始（赤色巨星へ）";
        } else if (!wasLateBurn && isLateBurnNow && !isEndState(starProps.ev)) {
            triggerEvent = true;
            eventType = 'lateBurn';
            currentEventMsg = isHighMass ? "重元素の核融合開始（He〜Si）" : "ヘリウム核融合開始";
        } else if (previousEv !== starProps.ev && isEndState(starProps.ev)) {
            triggerEvent = true;
            if (starProps.ev === 'wd') {
                eventType = 'wd';
                currentEventMsg = "惑星状星雲 / 白色矮星";
            } else {
                eventType = 'sn_event';
                currentEventMsg = "超新星爆発";
            }
        }
    }

    if (!triggerEvent) {
        if (isEndState(starProps.ev)) {
            if (starProps.ev === 'wd') currentEventMsg = "白色矮星";
            else if (starProps.ev === 'sn') currentEventMsg = "超新星残骸";
            else if (starProps.ev === 'ns') currentEventMsg = "中性子星";
            else if (starProps.ev === 'bh') currentEventMsg = "ブラックホール";
        } else if (isLateBurnNow) {
            currentEventMsg = isHighMass ? "重元素の核融合開始（He〜Si）" : "ヘリウム核融合開始";
        } else if (isGiantNow) {
            currentEventMsg = isHighMass ? "水素殻燃焼開始（赤色超巨星へ）" : "水素殻燃焼開始（赤色巨星へ）";
        } else {
            currentEventMsg = "水素核融合開始（主系列星）";
        }
    }

    wasGiant = isGiantNow;
    wasLateBurn = isLateBurnNow;
    previousEv = starProps.ev;

if (triggerEvent) {
        terminalEv = starProps.ev;
        
        if (isPlaying) {
            if (eventType === 'wd') {
                isPlanetaryNebula = true; pnTime = 0.0;
                isSupernova = false; isGeneralEvent = false;
                if (!isCompareMode) { // 【追加】比較モード時はズームしない
                    targetZoom = 3.0;
                    isAutoZooming = true;
                }
            } else if (eventType === 'sn_event') {
                isSupernova = true; snTime = 0.0;
                isPlanetaryNebula = false; isGeneralEvent = false;
                if (!isCompareMode) { // 【追加】比較モード時はズームしない
                    if (terminalEv === 'ns' || terminalEv === 'bh') targetZoom = 15.0;
                    else targetZoom = 4.0;
                    isAutoZooming = true;
                }
            } else {
                setPlaying(false);
                isGeneralEvent = false;
                isSupernova = false; isPlanetaryNebula = false;
            }
        } else {
            isEventPlaying = true;
            eventPhase = rawPhase;
            
            phaseSlider.disabled = true;
            massSlider.disabled = true;
            zSlider.disabled = true;
            
            if (eventType === 'wd') {
                isPlanetaryNebula = true; pnTime = 0.0;
                isSupernova = false; isGeneralEvent = false;
                if (!isCompareMode) { // 【追加】比較モード時はズームしない
                    targetZoom = 4.0;
                    isAutoZooming = true;
                }
            } else if (eventType === 'sn_event') {
                isSupernova = true; snTime = 0.0;
                isPlanetaryNebula = false; isGeneralEvent = false;
                if (!isCompareMode) { // 【追加】比較モード時はズームしない
                    if (terminalEv === 'ns' || terminalEv === 'bh') targetZoom = 15.0;
                    else targetZoom = 4.0;
                    isAutoZooming = true;
                }
            } else {
                isGeneralEvent = true; generalEventTimer = 0.0;
                isSupernova = false; isPlanetaryNebula = false;
            }
        }
    }

    if (!isEndState(starProps.ev) && !isSupernova && camera3D && camera3D.zoom !== 1.0) {
        targetZoom = 1.0;
        isAutoZooming = true;
    }

    const ageEl = document.getElementById('age-value');
    const eventEl = document.getElementById('event-value');
    const realAge = starProps.age * 1000000000;

    if (ageEl) ageEl.innerText = formatAge(realAge);
    if (eventEl) eventEl.innerText = currentEventMsg;

    const displayScale = getDisplayRadius(starProps.radius);
    const realScale = starProps.radius;


// 【追加】3Dビュー上の半径・質量ラベルの更新処理をここに移動（正しい mappedPhase と starProps を使用）
    const rLabel = document.getElementById('info-radius');
    const mLabel = document.getElementById('info-mass');
    if (rLabel && mLabel) {
        const isEndState = (starProps.ev === 'wd' || starProps.ev === 'ns' || starProps.ev === 'bh');
        
        if (isEndState) {
            const radiusKm = (starProps.radius * 695700).toLocaleString(undefined, {maximumFractionDigits: 0});
            rLabel.innerText = `半径 = ${radiusKm} km`;
        } else {
            rLabel.innerText = `半径 = ${starProps.radius.toFixed(2)} R☉`;
        }
        
        mLabel.innerText = `質量 = ${getEstimatedMass(mass, mappedPhase, starProps.ev)} M☉`;
    }




let displayEv = starProps.ev;

    if (isEventPlaying || isSupernova || isPlanetaryNebula) {
        if (isSupernova && snTime < 1.5) displayEv = 'sn';
        else if (isPlanetaryNebula && pnTime < 1.5) displayEv = 'wd';
    }

    // 終末天体かどうかの判定用フラグ
    const isEndObj = (displayEv === 'wd' || displayEv === 'ns' || displayEv === 'bh' || displayEv === 'sn');
    
    const bodyScale = isCompareMode ? realScale : displayScale;
    // 終末天体の場合は、比較モードでも中心(Y=0)に固定する
    const bodyY = (isCompareMode && !isEndObj) ? realScale : 0;
    
    if (displayEv === 'wd') {
        isBlackHole = false;
        if (starMesh) starMesh.visible = false;
        if (wdMesh) wdMesh.visible = true;
        if (nsGroup) nsGroup.visible = false;
        if (snQuad) snQuad.visible = false;
        if (pnQuad) pnQuad.visible = isPlanetaryNebula;

        if (wdMesh) wdMesh.material.uniforms.baseColor.value.copy(temperatureToColor(starProps.temp));

        targetVisualScale = bodyScale; currentVisualScale = bodyScale;
        targetVisualY = bodyY;         currentVisualY = bodyY;
        targetMarkerScale = displayScale; currentMarkerScale = displayScale;

    } else if (displayEv === 'bh') {
        isBlackHole = true;
        if (starMesh) starMesh.visible = false;
        if (wdMesh) wdMesh.visible = false;
        if (nsGroup) nsGroup.visible = false;

        if (snQuad) snQuad.visible = isSupernova;

        if (pnQuad) pnQuad.visible = false;
        
        // 真っ黒バグ解消のため displayScale ではなく bodyScale に統一
        if (raymarchMaterial) raymarchMaterial.uniforms.bhScale.value = bodyScale;

        targetVisualScale = bodyScale; currentVisualScale = bodyScale;
        targetVisualY = bodyY;         currentVisualY = bodyY;
        targetMarkerScale = displayScale; currentMarkerScale = displayScale;




    } else if (displayEv === 'sn' || displayEv === 'ns') {
        isBlackHole = false;
        if (starMesh) starMesh.visible = false;
        if (wdMesh) wdMesh.visible = false;
        if (pnQuad) pnQuad.visible = false;
        if (snQuad) snQuad.visible = isSupernova;
        
        // 【修正】ここでも displayScale ではなく bodyScale に統一する
        if (raymarchMaterial) raymarchMaterial.uniforms.bhScale.value = bodyScale;
        
        // 【修正】比較モード時は正確な実寸(bodyScale)を使用。通常モード時は視認性のため下限0.02を設定。
        let nsScale = isCompareMode ? bodyScale : Math.max(displayScale, 0.02); 
        if (nsGroup) nsGroup.scale.set(nsScale, nsScale, nsScale);

        if (!isSupernova || snTime > 0.4) {
            if (terminalEv === 'bh' || displayEv === 'bh') {
                isBlackHole = true; 
                if (nsGroup) nsGroup.visible = false;
            } else if (terminalEv === 'ns' || displayEv === 'ns') {
                isBlackHole = false; 
                if (nsGroup) nsGroup.visible = true;
            }
        } else {
            isBlackHole = false; 
            if (nsGroup) nsGroup.visible = false;
        }

        targetVisualScale = bodyScale; currentVisualScale = bodyScale;
        targetVisualY = bodyY;         currentVisualY = bodyY;
        targetMarkerScale = displayScale; currentMarkerScale = displayScale;

    } else {
        isBlackHole = false;
        if (wdMesh) wdMesh.visible = false;
        if (nsGroup) nsGroup.visible = false;
        if (snQuad) snQuad.visible = false;
        if (pnQuad) pnQuad.visible = false;

        if (starMesh) {
            starMesh.visible = true;
            const starColor = temperatureToColor(starProps.temp);
            starMesh.material.uniforms.baseColor.value.copy(starColor);
            starMesh.material.uniforms.noiseScale.value = calculateNoiseScale(starProps.radius);
            
            const dynamicThreshold = Math.max(0.2, 0.8 - (starProps.radius * 0.002));
            starMesh.material.uniforms.sunspotThreshold.value = dynamicThreshold;
            starMesh.material.uniforms.sunspotSize.value = 5.0 / Math.pow(starProps.radius, 0.4);
            
            const isGiant = starProps.radius > 5.0;
            starMesh.material.uniforms.sunspotIntensity.value = isGiant ? 1.0 : 0.6;
        }
        
        targetVisualScale = bodyScale;
        targetVisualY = bodyY;
        targetMarkerScale = displayScale; 
        
        if (!isVisualScaleInitialized) {
            currentVisualScale = targetVisualScale;
            currentVisualY = targetVisualY;
            currentMarkerScale = targetMarkerScale;
            isVisualScaleInitialized = true;
        }
    }

    if (markerMesh) {
        const container = document.getElementById('hr-3d-container');
        if (container) {
            const starColor = temperatureToColor(starProps.temp);
            markerMesh.material.uniforms.baseColor.value.copy(starColor);
            markerMesh.material.uniforms.noiseScale.value = calculateNoiseScale(starProps.radius);
            
            targetMarkerX = getHrX(starProps.temp, container.clientWidth);
            targetMarkerY = container.clientHeight - getHrY(starProps.lum, container.clientHeight);

            if (!isMarkerPosInitialized) {
                currentMarkerX = targetMarkerX;
                currentMarkerY = targetMarkerY;
                isMarkerPosInitialized = true;
            }

            if (displayEv === 'wd' || displayEv === 'bh' || displayEv === 'sn' || displayEv === 'ns') {
                currentMarkerX = targetMarkerX;
                currentMarkerY = targetMarkerY;
            }
        }
    }



    if (isCompareMode) {
        if (displayEv === 'wd') {
            targetZoom = 40.0; // 【修正】太陽を1.0にしたためズームを半分に(80->40)
            if (camera3D) camera3D.position.z = 100;
        } else if (displayEv === 'ns' || displayEv === 'bh') {
            targetZoom = 20000.0; // 【修正】40000->20000
            if (camera3D) camera3D.position.z = 100;

        } else if (displayEv === 'sn') {
            // 【修正】超新星イベント中も、次にできる天体に合わせて比較モード用のズームを事前に行う
            if (terminalEv === 'ns' || terminalEv === 'bh') targetZoom = 20000.0;
            else targetZoom = 40.0;

        } else {
            targetZoom = 0.5; // 【修正】1.0->0.5
            if (camera3D) camera3D.position.z = 3000;
        }
        isAutoZooming = true;
    } else {
        // 【修正】通常(ズーム)モード時の適正倍率も設定し、モード切替時のバグを防ぐ
        if (displayEv === 'wd') {
            targetZoom = 4.0;
        } else if (displayEv === 'ns' || displayEv === 'bh') {
            targetZoom = 15.0;
        } else if (displayEv === 'sn') {
            if (terminalEv === 'ns' || terminalEv === 'bh') targetZoom = 15.0;
            else targetZoom = 4.0;
        } else {
            targetZoom = 1.0; 
        }
        isAutoZooming = true;
    }



    drawTrajectory(mass, z, mappedPhase);
}

function temperatureToColor(temp) {
    let tempK = temp / 100;
    let r, g, b;

    if (tempK <= 66) {
        r = 255;
        g = 99.47 * Math.log(tempK) - 161.11;
        if (tempK <= 19) {
            b = 0;
        } else {
            b = 138.51 * Math.log(tempK - 10) - 305.04;
        }
    } else {
        r = 329.69 * Math.pow(tempK - 60, -0.1332);
        g = 288.12 * Math.pow(tempK - 60, -0.0755);
        b = 255;
    }

    const clamp = (val) => Math.min(255, Math.max(0, val));
    return new THREE.Color(clamp(r) / 255, clamp(g) / 255, clamp(b) / 255);
}


function getEstimatedMass(mass, phase, ev) {
    if (ev === 'wd') return (0.11 * mass + 0.4).toFixed(2);
    if (ev === 'ns') return "1.40"; 
    if (ev === 'bh') return (mass * 0.2).toFixed(2);
    return (mass * (1.0 - phase * 0.05)).toFixed(2);
}


function getDisplayRadius(realRadius) {
    return Math.pow(realRadius, 0.33);
}

function drawTrajectory(mass, z, currentPhase) {
    if (!stellarData) return; 

    const canvas = document.getElementById('hr-trajectory-canvas');
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    const width = canvas.width;
    const height = canvas.height;

    ctx.clearRect(0, 0, width, height);

    ctx.strokeStyle = '#ffaa00';
    ctx.lineWidth = 2;
    ctx.beginPath();

    const steps = 100;
    for (let i = 0; i <= steps; i++) {
        const p = currentPhase * (i / steps);
        const props = calculateEvolutionFromData(mass, z, p);
        const x = getHrX(props.temp, width);
        const y = getHrY(props.lum, height);

        if (i === 0) {
            ctx.moveTo(x, y);
        } else {
            ctx.lineTo(x, y);
        }
    }
    ctx.stroke();
}

async function loadStellarData() {
    try {
        console.log("stellar_data.js からデータを読み込み中...");
        // HTML側で読み込まれた変数 stellarDataLocal を代入する
        if (typeof stellarDataLocal !== 'undefined') {
            stellarData = stellarDataLocal;
            console.log("データの読み込みが完了しました！");
        } else {
            throw new Error("stellarDataLocal が定義されていません。");
        }
    } catch (e) {
        console.error("データの読み込みに失敗しました:", e);
        alert("stellar_data.js の読み込みに失敗しました。ファイルが正しく変更・配置されているか確認してください。");
    }
}

function calculateEvolutionFromData(mass, z, phase) {
    if (!stellarData) return { temp: 5778, lum: 1, radius: 1, age: 0, ev: "normal" };

    const zKeys = Object.keys(stellarData).map(Number).sort((a, b) => a - b);
    let z1 = zKeys[0], z2 = zKeys[zKeys.length - 1];
    
    if (z <= zKeys[0]) {
        z1 = z2 = zKeys[0];
    } else if (z >= zKeys[zKeys.length - 1]) {
        z1 = z2 = zKeys[zKeys.length - 1];
    } else {
        for (let i = 0; i < zKeys.length - 1; i++) {
            if (z >= zKeys[i] && z <= zKeys[i + 1]) {
                z1 = zKeys[i];
                z2 = zKeys[i + 1];
                break;
            }
        }
    }

    const mKeys = Object.keys(stellarData[z1]).map(Number).sort((a, b) => a - b);
    let closestM = mKeys[0];
    let minDiff = Infinity;
    for (let m of mKeys) {
        let diff = Math.abs(m - mass);
        if (diff < minDiff) {
            minDiff = diff;
            closestM = m;
        }
    }

    const dataZ1 = stellarData[z1][closestM];
    const dataZ2 = stellarData[z2][closestM];

    let phaseIndex = Math.round(phase * 100);
    if (phaseIndex < 0) phaseIndex = 0;
    if (phaseIndex > 100) phaseIndex = 100;

    const resZ1 = dataZ1[phaseIndex];
    const resZ2 = dataZ2[phaseIndex];

    let finalTemp, finalLum, finalRadius, finalAge, finalEv;

    if (z1 === z2) {
        finalTemp = resZ1.T;
        finalLum = resZ1.L;
        finalRadius = resZ1.R;
        finalAge = resZ1.age;
        finalEv = resZ1.ev;
    } else {
        let zT = (z - z1) / (z2 - z1);
        finalTemp = resZ1.T * (1 - zT) + resZ2.T * zT;
        finalLum = resZ1.L * (1 - zT) + resZ2.L * zT;
        finalRadius = resZ1.R * (1 - zT) + resZ2.R * zT;
        finalAge = resZ1.age * (1 - zT) + resZ2.age * zT;
        finalEv = resZ1.ev;
    }

    if (finalEv === 'wd') {
        finalTemp = 30000;  
        finalRadius = 0.01; 
        finalLum = Math.pow(finalRadius, 2) * Math.pow(finalTemp / 5778, 4);
    } else if (finalEv === 'ns' || finalEv === 'bh') {
        finalTemp = 100000;
        finalRadius = 0.00001; 
        finalLum = 0.00001; 
    }

    return {
        temp: finalTemp,
        lum: finalLum,
        radius: finalRadius,
        age: finalAge,
        ev: finalEv
    };
}

function formatAge(age) {
    if (age < 10000) return Math.round(age) + " 年";
    if (age < 100000000) return (age / 10000).toFixed(1) + " 万年";
    return (age / 100000000).toFixed(2) + " 億年";
}

function formatEvent(ev) {
    switch(ev) {
        case 'normal': return '主系列星〜巨星';
        case 'wd': return '白色矮星';
        case 'sn': return '超新星爆発';
        case 'ns': return '中性子星';
        case 'bh': return 'ブラックホール';
        default: return '不明';
    }
}

function calculateNoiseScale(radius) {
    let nScale = 30.0 / Math.pow(radius, 0.45);
    return Math.max(1.5, Math.min(nScale, 60.0));
}

// ==========================================
// 3. イベントリスナーと初期化プロセス
// ==========================================

window.addEventListener('resize', () => {
    updateAppScale();

    drawHRBackground();

    const container = document.getElementById('hr-canvas-container');
    const trajCanvas = document.getElementById('hr-trajectory-canvas');
    if (trajCanvas && container) {
        trajCanvas.width = container.clientWidth;
        trajCanvas.height = container.clientHeight;
        updateSimulation(); 
    }

// 【修正】HR図の3D空間のカメラとレンダラーのサイズを更新し、基準星を再配置する
    if (cameraHR3D && rendererHR3D && container) {
        const width = container.clientWidth;
        const height = container.clientHeight;
        
        cameraHR3D.right = width;
        cameraHR3D.top = height;
        cameraHR3D.updateProjectionMatrix();
        
        rendererHR3D.setSize(width, height);
        updateHR3DPositions(); // 基準星の位置を取り残されないように再計算
    }


    if (camera3D && renderer3D) {
        const container3D = document.getElementById('star-3d-container');
        if (container3D) {
            const width = container3D.clientWidth;
            const height = container3D.clientHeight;
            const aspect = width / height;
            
            const viewSize = 3;
            camera3D.left = -viewSize * aspect;
            camera3D.right = viewSize * aspect;
            camera3D.top = viewSize;
            camera3D.bottom = -viewSize;
            camera3D.updateProjectionMatrix();
            
            renderer3D.setSize(width, height);
            
            if (bgRenderTarget) bgRenderTarget.setSize(width, height); 
            if (snMaterial) snMaterial.uniforms.aspectRatio.value = width / height;
            if (pnMaterial) pnMaterial.uniforms.aspectRatio.value = width / height; 
        }
    }
});

window.onload = async () => {
    const container = document.getElementById('hr-canvas-container');
    const trajCanvas = document.getElementById('hr-trajectory-canvas');
    if (trajCanvas && container) {
        trajCanvas.width = container.clientWidth;
        trajCanvas.height = container.clientHeight;
    }

    drawHRBackground();
    init3DViewer();
    initHR3DViewer();

    const sliders = ['mass-slider', 'metallicity-slider'];
    sliders.forEach(id => {
        const el = document.getElementById(id);
        if (el) el.addEventListener('input', updateSimulation);
    });
    
    const phaseSlider = document.getElementById('phase-slider');
    if (phaseSlider) {
        phaseSlider.addEventListener('input', () => {
            if (isPlaying) setPlaying(false);
            updateSimulation();
        });
    }

    const playBtn = document.getElementById('play-btn');
    if (playBtn) {
        playBtn.addEventListener('click', () => {
            setPlaying(!isPlaying);
        });
    }

    const resetBtn = document.getElementById('reset-btn');
    if (resetBtn) {
        resetBtn.addEventListener('click', () => {
            setPlaying(false); 
            if (phaseSlider) {
                phaseSlider.value = 0;
                previousRawPhase = -1; 

// カメラズームと天体のスケールを即座にリセット（補間をスキップ）
                targetZoom = 1.0;
                if (camera3D) {
                    camera3D.zoom = 1.0;
                    camera3D.updateProjectionMatrix();
                }
                currentVisualScale = 1.0;
                currentVisualY = 0;

                updateSimulation();
            }
        });
    }
    
    const zoomBtn = document.getElementById('mode-zoom-btn');
    const compareBtn = document.getElementById('mode-compare-btn');

if (zoomBtn && compareBtn) {
        zoomBtn.addEventListener('click', () => {
            isCompareMode = false;
            zoomBtn.classList.add('active');
            compareBtn.classList.remove('active');
            if (sunMesh) sunMesh.visible = false;
            
            if (controls3D) {
                controls3D.enabled = true; 
                controls3D.target.set(0, 0, 0);
            }
            if (camera3D) {
                camera3D.position.set(0, 30, 100);
                camera3D.lookAt(0, 0, 0);
            }
            
            updateSimulation();
            
            // 【修正】補間をスキップして即座にズームとスケールを反映（スナップ）
            if (camera3D) {
                camera3D.zoom = targetZoom;
                camera3D.updateProjectionMatrix();
            }
            currentVisualScale = targetVisualScale;
            currentVisualY = targetVisualY;
        });

        compareBtn.addEventListener('click', () => {
            isCompareMode = true;
            compareBtn.classList.add('active');
            zoomBtn.classList.remove('active');
            if (sunMesh) sunMesh.visible = true;
            
            if (controls3D) {
                controls3D.enabled = false; 
                controls3D.target.set(0, 0, 0);
            }
            if (camera3D) {
                camera3D.position.set(0, 0, 3000);
                camera3D.lookAt(0, 0, 0);
            }
            
            updateSimulation();
            
            // 【修正】補間をスキップして即座にズームとスケールを反映（スナップ）
            if (camera3D) {
                camera3D.zoom = targetZoom;
                camera3D.updateProjectionMatrix();
            }
            currentVisualScale = targetVisualScale;
            currentVisualY = targetVisualY;
        });
    }

    if (container) {
        container.addEventListener('wheel', (event) => {
            event.preventDefault(); 
            
            const zoomDelta = event.deltaY * -0.001;
            const newZoom = Math.max(0.1, Math.min(hrZoom + zoomDelta, 10.0)); 
            
            const rect = container.getBoundingClientRect();
            const mouseX = (event.clientX - rect.left) / currentScale;
            const mouseY = (event.clientY - rect.top) / currentScale;
            
            hrPanX -= (mouseX - container.clientWidth / 2 - hrPanX) * (newZoom / hrZoom - 1);
            hrPanY -= (mouseY - container.clientHeight / 2 - hrPanY) * (newZoom / hrZoom - 1);
            
            hrZoom = newZoom;
            
            drawHRBackground();
            updateSimulation();
            updateHR3DPositions();
        });

        container.addEventListener('mousedown', (event) => {
            isDraggingHR = true;
            previousMousePosition = { x: event.clientX, y: event.clientY };
            container.style.cursor = 'grabbing'; 
        });

        window.addEventListener('mousemove', (event) => {
            if (!isDraggingHR) return;
            
            const deltaX = (event.clientX - previousMousePosition.x) / currentScale;
            const deltaY = (event.clientY - previousMousePosition.y) / currentScale;
            
            hrPanX += deltaX;
            hrPanY += deltaY;
            previousMousePosition = { x: event.clientX, y: event.clientY };
            
            drawHRBackground();
            updateSimulation();
            updateHR3DPositions();
        });

        window.addEventListener('mouseup', () => {
            isDraggingHR = false;
            container.style.cursor = 'default';
        });
    }

    await loadStellarData();
    updateSimulation();
    updateHR3DPositions(); 

    updateAppScale();


    // レンダリング完了後に一度リサイズイベントを発火して表示崩れを防ぐ
    window.dispatchEvent(new Event('resize'));
};